/*! JSCommonUtil - v1.1.0 - 2014-08-11
* http://git.oschina.net/timzaak/JSCommonUtil
* Copyright (c) 2014 Licensed MIT */
(function(window,document){
var VERSION="1.1.0";
/**
 *
 * 项目中内部工具类,
 * 封装项目中使用的各种通用函数
 *
 * @namespace commonUtil
 *
 */

/**
 * 所有关于路径和数据的相关操作
 * @namespace pathUtil
 * @memberOf commonUtil
 */

/**
 * throw Error to stop executing and return some useful information
 * from angularJS
 * @memberOf commonUtil
 * @example
 * var moduleErr=minErr("moduleName")
 * moduleErr("some thing err, may it be {1} or be {2}",$info1,$info2)
 * @param  {string} module 模块名称
 * @return {function}
 */
function minErr(module) {
    return function() {
        var message = arguments[0],
            matches;
        matches = message.match(/\{\d+\}/g);
        if (undefined !== arguments[1])
            for (var i = 0; i < matches.length; i++) {
                message = message.replace(matches[i], arguments[parseInt(matches[i].slice(1, -1))]);
            }
        message = "module: " + module + " message: " + message;
        throw new Error(message);
    };
}

/**
 * 整个项目的入口,所有东西的入口
 *
 * @namespace jc
 *
 */
jc = window.jc = {
    /**
     * refer to {@link documentParse.render}
     * @function render
     * @memberOf jc
     */
    render: render,
    /**
     * refer to {@link commonUtil.minErr}
     * @function err
     * @memberOf jc
     */
    err: minErr,
    /**
     * refer to {@link commonUtil.getType}
     * @memberOf jc
     */
    getType: getType,
    /**
     * refer to {@link tempHelper.tempHelper}
     * @function th
     * @memberOf jc
     */
    th: tempHelper,
    /**
     * refer to {@link pubsub.initPB}
     * @function
     * @memberOf jc
     */
    initPB: initPB,
    /**
     * refer to {@link commonUtil.mix}
     * @memberOf jc
     */
    mix: mix,

    util:{
        isInDocument:isInDocument,
        nextUid:$nextUid
    }
};

/**
 * 用来做js继承
 * 代码中有具体注释，可参阅源码
 * 在测试中也有关于mix的应用
 * @example
 * var child ={child:childFun}
 * jc.mix(true,child,{p:parentFun})
 * //child 就变为  {child:childFun,p:parentFun}
 * @memberOf commonUtil
 * @param {boolean=} [isDeep=false] 是否是深度copy
 * @param {*} child
 * @param {*} parent
 *
 */
function mix() {
    var options, name, src, copy, copyIsArray, clone,
        target = arguments[0] || {},
        i = 1,
        length = arguments.length,
        deep = false

    // 如果第一个参数为布尔,判定是否深拷贝
    if (typeof target === "boolean") {
        deep = target
        target = arguments[1] || {}
        i++
    }

    //确保接受方为一个复杂的数据类型
    if (typeof target !== "object" && getType(target) !== "function") {
        target = {}
    }

    //如果只有一个参数，那么新成员添加于mix所在的对象上
    if (i === length) {
        target = this
        i--
    }

    for (; i < length; i++) {
        //只处理非空参数
        if ((options = arguments[i]) != null) {
            for (name in options) {
                src = target[name]
                copy = options[name]

                // 防止环引用
                if (target === copy) {
                    continue;
                }
                if (deep && copy && (isPlainObject(copy) || (copyIsArray = Array.isArray(copy)))) {

                    if (copyIsArray) {
                        copyIsArray = false
                        clone = src && Array.isArray(src) ? src : []

                    } else {
                        clone = src && isPlainObject(src) ? src : {}
                    }

                    target[name] = jc.mix(deep, clone, copy)
                } else if (copy !== void 0) {
                    target[name] = copy
                }
            }
        }
    }
    return target
}

//配置
var config = jc.config = {
    tplRootUrl: './views/'
};
// function invokeLater() {
//     return function() {
//         invokeQueue[insertMethod || 'push']([provider, method, arguments]);
//         return moduleInstance;
//     };
// }

var jcError = minErr('jc');
//jquery引入 判定
if (!jQuery) {
    jcError("jc depends on jQuery");
}

var rword = /[^, ]+/g;

//类型判定
var class2type = {};
var serialize = Object.prototype.toString;
"Boolean Number String Function Array Date RegExp Object Error".replace(rword, function(name) {
    class2type["[object " + name + "]"] = name.toLowerCase()
});
/**
 * 获取具体类型,并返回小写的类型,支持
 * Boolean Number String Function Array Date RegExp Object Error
 * 的判定
 * @memberOf commonUtil
 * @example
 * var a={}
 * getType(a)//object
 * var reg = \abc\
 * getType(reg) //regexp
 *
 * @param  {*} unKnown
 * @return {String}
 */
function getType(obj) { //取得类型
    if (obj == null) {
        return String(obj)
    }
    // 早期的webkit内核浏览器实现了已废弃的ecma262v4标准，可以将正则字面量当作函数使用，因此typeof在判定正则时会返回function
    return typeof obj === "object" || typeof obj === "function" ?
        class2type[serialize.call(obj)] || "object" :
        typeof obj
}



var noop = function() {};

var UniObj = {};

var documentFragment = document.createDocumentFragment();


/**
 * 将array转换成object类型
 * array值将变成object 的key
 * val将变成object 的value值
 *
 *
 * @memberOf commonUtil
 * @param  {array} array
 * @param  {*} val
 * @return {objectd}
 */
function oneObject(array, val) {
    if (typeof array === "string") {
        array = array.match(rword) || []
    }
    var result = {},
        value = val !== void 0 ? val : 1
    for (var i = 0, n = array.length; i < n; i++) {
        result[array[i]] = value
    }
    return result
}
/**
 *
 * 通过路径获取具体的值
 * @memberOf pathUtil
 * @example
 * var obj={
 *     a:{
 *         b:"aa",
 *         c:[1,2,3,4]
 *     }
 * }
 * dotPath(obj,"a.b")//"aa"
 * dotPath(obj,"a.c.2")// 3
 * @param  {object} object data
 * @param  {string} path
 * @return {*}
 */
function dotPath(object, path) {
    var re = object;
    path = path.split('.')
    for (var i = 0; i < path.length; i++) {
        if (re[path[i]] === undefined) {
            return;
        } else {
            re = re[path[i]]
        }
    }
    return re
}
/**
 *
 * not support array path
 * 根据索引的路径进行赋值
 * @example
 * var object={
 *     a:{
 *         b:3
 *     }
 * }
 * dotPathVal(object,"a.b",1)
 * //object    {a:{b:1}}
 * @memberOf pathUtil
 * @param  {object} object data
 * @param  {string} path
 * @param  {*} val    the value to be evaluated
 */
function dotPathVal(object, path, val) {
    var pathArr = path.match(/[^.]+/g),
        re = object,
        length = pathArr.length - 1;
    for (var i = 0; i < length; i++) {
        re = re[pathArr[i]]
    }
    re[pathArr[length]] = val
}

/**
 * @typedef {Object} pathResult
 * @property {object|array} obj 父引用
 * @property {*} value  具体值
 */
/**
 * 根据数组路径来返回值和其父引用
 * @memberOf pathUtil
 * @param  {object} object
 * @param  {array|string} path
 * @return {pathResult}
 */
function arrPathRef(object, path) {
    if (typeof path === 'string') {
        path = path.split('.')
    }
    var length = path.length - 1,
        re = object;
    try {
        for (var i = 0; i < length; i++) {
            re = re[path[i]]
        }
    } catch (e) {
        minErr("path Error")("the path:'{1}' can refer correct data {2}", path.join('.'), JSON.stringify(object))
    }
    return {
        obj: re,
        ref: path[length]
    }
}

function arrPathVal(object, arrPath, value) {
    var re = arrPathRef(object, arrPath)
    if (re.obj instanceof Array && typeof re.obj.set === 'function') {
        re.obj.set(re.ref, value)
    } else
        re.obj[re.ref] = value
}

/**
 * 遍历出最深层的数据路径
 * @memberOf pathUtil
 * @param  {object} o
 * @return {array}  path    路径集合
 */
function collectionPath(o, path) {
    var re = [],
        key,
        type;
    path = path || []
    type = getType(o)
    if (type === "object") {
        for (key in o) {
            if (o.hasOwnProperty(key)) {
                type = getType(o[key])
                path.push(key)
                //re.push(path.concat())
                if (type === "object" || type === "array") {
                    re = re.concat(collectionPath(o[key], path))
                } else {
                    re.push(path.concat())
                }
                path.splice(path.length - 1, 1)
            }
        }
    } else if (type === "array") {
        o.forEach(function(v, i, arr) {
            path.push(i)
            // re.push(path.concat())
            type = getType(v)
            if (type === "object" || type === "array") {
                re = re.concat(collectionPath(o[i], path))

            } else {
                re.push(path.concat())
            }
            path.splice(path.length - 1, 1)

        })
    }
    return re;
}

/**
 * 判定是否在指定document下
 * @param  {node=}  parent 默认为document.body
 * @param  {node}  child
 * @return {Boolean}
 */
function isInDocument(parent, child) {
    if (arguments.length === 1) {
        child = parent
        parent = document.body
    }
    if (child) {
        while (parent!==child) {
            child=child.parentNode
            if (!child ) {
                return false
            }
        }
        return true
    }
    return false
}

function isPlainObject(obj) {
    return !!obj && typeof obj === "object" && Object.getPrototypeOf(obj) === Object.prototype
}

//unique Id generator

var $$uid = ["0", "0", "0"];
/**
 * 生成本框架作用域内的唯一id字符串
 * @memberOf commonUtil
 * @return {string}
 */
function $nextUid() {
    var index = $$uid.length;
    var digit;
    while (index) {
        index--;
        digit = $$uid[index].charCodeAt(0);
        if (digit == 57 /*'9'*/ ) {
            $$uid[index] = 'A';
            return $$uid.join('');
        }
        if (digit == 90 /*'Z'*/ ) {
            $$uid[index] = '0';
        } else {
            $$uid[index] = String.fromCharCode(digit + 1);
            return $$uid.join('');
        }
    }
    $$uid.unshift('0');
    return $$uid.join('');
}



/**
 *
 * get parent path, the path format likes this "a.b.e.1"，
 * and we can get "a.b.e"
 * so we only need to remove the last dot
 * @memberOf pathUtil
 * @param  {String} strPath
 * @return {String}
 */
function getParentPath(strPath) {
    return strPath.replace(/(.*)(\..*?)$/, "$1")
}
// function getAttributeNodes(elem) {
//     var re = {};
//     for (var i = 0, len = elem.attributes.length; i < len; i++) {
//         var name = elem.attributes[i].nodeName;
//         var value = elem.attributes[i].nodeValue;
//         if (elem.attributes[i].specified)
//             re[name] = value
//     }
//     return re
// }
var tempError = minErr('templateHelper');
var $attrTag = 'jcinner-temp-status',
    $jcData = "jc" + VERSION.replace(/[\.\-_]/g, "");

var AP = Array.prototype;

function removeChildrenPathBind(id, path) {

    var reg = new RegExp("^" + (path + '.').replace(/\./g, "\\.")),
        watchPB = $watchPB(id),
        thPB = tempHelper.$pb(id),
        watchPath = path + ".length";
    watchPB.eventNamesWithReg(reg).forEach(function(eventName) {
        if (eventName !== watchPath)
            watchPB.unbind(eventName)
    })
    thPB.eventNamesWithReg(reg).forEach(function(eventName) {
        thPB.unbind(eventName)
    })
}

function tempHelperPupEvent(id, path, message) {
    tempHelper.$pb(id).publish(path, message)
    if (Array.isArray(message)) {

        $watchPB(id).publish(path + '.length', message)
    }
    $watchPB(id).publish(path, message)
}

var fakeArrayMethod = {
    set: function(k, v) { //god method ... bad implement
        if (this[k] !== v) {
            if (typeof v === 'object') {
                v.$index = k
                jc.mix(this[k], v)
            } else {
                this[k] = v
                this.$index = k
            }
            tempHelperPupEvent(this.$id, this.$parentPath + "." + k, this[k])
        }
    },
};

"splice,pop,shift,unshift,push".replace(rword, function(method) {
    fakeArrayMethod[method] = function() {
        var re = AP[method].apply(this, arguments)
        removeChildrenPathBind(this.$id, this.$parentPath, this)
        tempHelperPupEvent(this.$id, this.$parentPath, this)
        return re
    }
})

function valueBind(thisData, id, path, targetPath, val, ref) {
    var tpathStr = targetPath.join(".")
    tempHelper.$pb(id).bind(tpathStr, (function(value) {
        arrPathVal(thisData, this.path, value)
    }).bind({
        path: path
    }))
    try {
        //if ("value" in Object.getOwnPropertyDescriptor(ref.obj, ref.ref)) {
        var getAndSet = function(value) {
            if (arguments.length === 0)
                return val
            else {
                if (value !== val) {
                    if (value instanceof Array) {
                        mix(value, fakeArrayMethod)
                        value.$id = id
                        value.$parentPath = val.$parentPath
                    }
                    val = value
                    tempHelperPupEvent(id, tpathStr, value)
                }
            }
        }
        Object.defineProperty(ref.obj, ref.ref, {
            get: getAndSet,
            set: getAndSet
        })
        //}
    } catch (e) {
        tempError("the key to be binded should not be undefined. path:{1}", path.join('.'))
    }
}


var tempHelperObj = {
    _data: function(key, value) {
        var isObj = getType(key) === 'object'

        if (arguments.length === 1 && !isObj)
            return (this.elem[$jcData] || UniObj)[key];

        if (!this.elem[$jcData])
            this.elem[$jcData] = {};
        if (isObj) {
            var k;
            for (k in key) {
                if (key.hasOwnProperty(k)) {
                    this.elem[$jcData][k] = key[k]
                }
            }
        } else {
            this.elem[$jcData][key] = value;
        }
    },
    data: function() {
        return this._data("data")
    },
    _hasData: function(key) {
        return key in (this.elem[$jcData] || UniObj);
    },
    _removeData: function(key) {
        if (this._hasData(key))
            delete this.elem[$jcData][key];
    },
    html: function(text) {
        if (text) {
            this.$div.html(text);
        } else {
            return this.$div.html();
        }
    },
    hide: function() {
        this.$div.hide()
    },

    show: function() {
        this.$div.show()
    },

    empty: function() {
        this.$div.empty()
        if (this._data("id")) {
            this._removeData("frags")
            $watchPB(this._data("id")).unbind('$all')
            this._removeData("data")
            this.unbind("$all")
        }
    },
    publish: function(pStr, value) {
        if (arrPathVal(pStr, this.data()) != value)
            tempHelper.$pb(this._data('id')).publish(pStr, value)
    },
    listen: function(source, divArray, target) {
        //----全都是路径的判定以及param的预备处理

        var thisData = this.data(),
            id,
            sourceType = getType(source);

        if (sourceType === "object") {
            source = collectionPath(source)
        } else if (sourceType === 'string') {
            source = [source.split(".")]
            if (target) {
                target = [target.split(".")]
            }
        } else if (sourceType === 'array') {
            source = source.map(function(v) {
                return v.split(".")
            })
            if (target) {
                target = target.map(function(v) {
                    return v.split(".")
                })
            }
        }
        if (arguments.length < 3) {
            target = jc.mix(true, [], source)
        }
        if (!divArray) {
            divArray = [this.$div]
        } else if (getType(divArray) !== 'array') {
            divArray = [divArray]
        }
        //----全都是路径的判定以及param的预备处理---结束

        for (var i = 0, tem; tem = divArray[i++];) {
            tem = tempHelper(tem)
            id = tem._data("id")
            var targetData = tem.data()
            source.forEach(function(path, index) {
                var targetPath = target[index],
                    ref = arrPathRef(targetData, targetPath);
                if (Array.isArray(ref.obj)) {
                    var tpathStr = targetPath.join(".")
                    if (typeof ref.obj.set !== 'function') {
                        var tparentPath = targetPath.slice(0, targetPath.length - 1)
                        var tparentPathStr = tparentPath.join(".")
                        var parentPath = path.slice(0, path.length - 1)
                        var parentRef = arrPathRef(targetData, tparentPath);
                        valueBind(thisData, id, parentPath, tparentPath, parentRef.obj[parentRef.ref], parentRef)
                        ref.obj.$id = id
                        ref.obj.$parentPath = tparentPathStr
                        mix(ref.obj, fakeArrayMethod)

                    }
                    tempHelper.$pb(id).bind(tpathStr, (function(value) {
                        arrPathVal(thisData, this.path, value)
                    }).bind({
                        path: path
                    }))

                } else {
                    var val = ref.obj[ref.ref];
                    if (val instanceof Array) {
                        val.$id = id
                        val.$parentPath = targetPath.join(".")
                        mix(val, fakeArrayMethod)
                    }
                    valueBind(thisData, id, path, targetPath, val, ref)
                }

            })

        }
    },

    bind: function(eventName, fnName, fn) {
        var id = this._data("id")
        var type = getType(eventName)
        if (type === 'string')
            tempHelper.$pb(id).bind(eventName, fnName, fn)

        else if (type === 'object') {
            var collection = collectionPath(eventName)
            for (var i = 0, c; c = collection[i++];) {
                tempHelper.$pb(id).bind(c.join("."), fnName, fn)
            }
        } else {
            eventName.forEach(function(v) {
                tempHelper.$pb(id).bind(v, fnName, fn)
            })
        }
        this.listen(eventName)

    },
    unbind: function(eventName, fnName) {
        var id = this._data("id")
        var type = getType(eventName)
        if (type === 'string')
            tempHelper.$pb(id).unbind(eventName, fnName)
        else if (type === 'object') {
            var collection = collectionPath(eventName)
            for (var i = 0, c; c = collection[i++];) {
                tempHelper.$pb(id).unbind(c.join("."), fnName)
            }
        } else {
            eventName.forEach(function(v) {
                tempHelper.$pb(id).unbind(v, fnName)
            })
        }


    },
    reRender: function(id, data) {
        var frag = this._data("frags")[id];
        jc.mix(true, this.data(), data)
        //frag.elem.style.visibility = "hidden"
        frag.elem.innerHTML = frag.tpl
        scanAttr(frag.elem, data, this)
        //frag.elem.style.visibility = ""

        frag.elem.style.display = ""
    },
    //用正则是否会更好点？
    getCleanData: function(cleanData, originData) {
        if (cleanData === undefined) {
            cleanData = {}
        }
        if (originData === undefined)
            originData = this.data()
        switch (getType(originData)) {
            case "object":
                for (var key in originData) {
                    switch (key) {
                        case "$index":
                        case "$path":
                            break;
                        default:
                            switch (getType(originData[key])) {
                                case "object":
                                    cleanData[key] = {}
                                    this.getCleanData(cleanData[key], originData[key])
                                    break;
                                case "array":
                                    cleanData[key] = []
                                    this.getCleanData(cleanData[key], originData[key])
                                    break;
                                default:
                                    cleanData[key] = originData[key]
                            }
                    }
                }

                break;
            case "array":
                for (var i = 0; i < originData.length; i++) {
                    switch (getType(originData[i])) {
                        case "object":
                            cleanData.push({})
                            this.getCleanData(cleanData[i], originData[i])
                            break;
                        case "array":
                            cleanData.push([])
                            this.getCleanData(cleanData[i], originData[i])
                            break;
                        default:
                            cleanData.push(originData[i])
                    }
                }
                break;
        }
        return cleanData
    }
};

/**
 *
 *
 * 关于模板的操作，都基于此。
 *
 * @namespace tempHelper
 * @namespace tempHelper
 * @param  {Element} $div         jquery selected result
 * @return {object}             the utils for operating the template
 */
function tempHelper($div) {
    var re;
    if ($div.length !== 1) {
        tempError('the container selected by jqury {1} is not unique or not exists, please check', $div.selector || $div)
    }

    re = {
        $div: $div,
        elem: $div[0]
    };

    for (var key in tempHelperObj) {
        if (tempHelperObj.hasOwnProperty(key) /*&& key.search(/^_/ )*/ ) {
            re[key] = tempHelperObj[key]
        }
    }

    return re;
}

tempHelper.$pb = initPB();
var arrError = minErr('ArrayHelper');
var arrayUtil = jc.Array = {
    groupBy: groupBy,
    objToArr: objToArr,
    isEqual: isEqual,
    group: group,
    getIndex: getIndex,
    contain: contain,
    getDistinct: getDistinct,
    replaceElem: replaceElem
};
/**
 * array groupBy function, and oops! the element of array should be
 * Object! we may discard this limitation laterly.
 * @param  {array} array
 * @param  {string} key     it can be "a.b.c"
 * @param  {function=} filter  filter the array element and return boolean ,it receive array element and index params
 * @return {object}        the object is "key:[after filter Array]"
 */
function groupBy(array, key, filter) {
    var rkey, result = {}

    if (filter) {
        array.forEach(function(elem, index) {
            if (filter(elem, index)) {
                rkey = dotPath(elem, key)
                rkey in result ? result[rkey].push(elem) : result[rkey] = [elem]
            }
        })
    } else {
        array.forEach(function(elem, index) {
            rkey = dotPath(elem, key)
            rkey in result ? result[rkey].push(elem) : result[rkey] = [elem]
        })
    }
    return result

}

/**
 *
 * normal group function , group by the groupFn result
 * @param  {array} array
 * @param  {function<elem,index>} groupFn
 * @return {object}
 */
function group(array, groupFn) {
    var result = {}
    array.forEach(function(elem, index) {
        var key = groupFn(elem, index)
        key in result ? result[key].push(elem) : result[key] = [elem]
    })
    return result
}

/**
 * transfer the object to array like this
 * [{key:...,value:...},{key:...,value:...}...]
 * @param  {object} object
 * @param  {string=} keyName   default value is 'key'
 * @param  {string=} valueName default value is 'value'
 * @return {array}
 */
function objToArr(object, keyName, valueName) {
    var key, re = [],
        tempObj;

    keyName = keyName || "key"
    valueName = valueName || "value"
    for (key in object) {
        if (object.hasOwnProperty(key)) {
            tempObj = {}
            tempObj[keyName] = key
            tempObj[valueName] = object[key]
            re.push(tempObj)
        }
    }
    return re;
}

/**
 *  assert the arrays have same element value
 * @param  {array}  arr1
 * @param  {array}  arr2
 * @param  {Boolean=}  deep
 * @return {Boolean}
 */
function isEqual(arr1, arr2, deep) {
    if (arr1.length !== arr2.length) {
        return false
    }
    for (var i = 0; i < arr1.length; i++) {
        if (deep && Array.isArray(arr1[i]) && Array.isArray(arr2[i])) {
            if (!isEqual(arr1[i], arr2[i], deep))
                return false
        } else {
            if (arr1[i] !== arr2[i])
                return false
        }
    }
    return true;
}

/**
 * 根据查询条件获取数组的 index,如果条件都不满足,返回-1
 * @example
 * var arr=[{key:"aa",key2:"bb".....},{key:"abc",key2....}]
 * jc.Array.getIndex(arr,key,"aa")//0
 * @param  {Array<Object>} arr
 * @param  {String|number} key   数组内对象的key
 * @param  {＊} value
 * @return {Number}
 */
function getIndex(arr, key, value) {
    for (var i = 0, elem; elem = arr[i]; i++) {
        if (elem[key] === value) {
            return i
        }
    }
    return -1
}

/**
 * 判定array 数组 是否存在值，支持 的数组格式有
 * ［1，2，3，3，4 ....］
 *  [{key:vlue},{key:value}...]
 *
 * @param  {Array<*>} arr
 * @param  {String=} key
 * @param  {*} value
 * @return {Boolean}
 */
function contain(arr, key, value) {
    var i = 0,
        elem;
    if (arr instanceof Array) {
        if (arguments.length === 2) {
            value = key
            for (; elem = arr[i++];) {
                if (value === elem) {
                    return true
                }
            }
            return false
        }
        while (elem = arr[i++]) {
            if (value === elem[key])
                return true
        }
        return false
    } else {
        arrError("the contain function only accept array param, please check if the first param is an Array")
        //throw new Exception("param invalid")
    }
}
/**
 * 过滤出数组中重复的元素，并返回一个新的不重复的数组，原数组不会被改变,
 * 注意，现在还不支持关于Array<Object>的深度判定，要有这方面需求的话，
 * 可自行扩展 。另外这个实现方法不算效率最高的。
 * @param  {Array} arr
 * @return {Array}
 */
function getDistinct(arr) {
    var distinctArr = []
    if (arr instanceof Array) {
        arr.forEach(function(elem) {
            if (!contain(distinctArr, elem)) {
                distinctArr.push(elem)
            }
        })
    } else {
        arrError("the getDistinct function only accept array param, please check if the first param is an Array")
    }
    return distinctArr
}
/**
 *  这个函数还可以往外扩展一个参数,getIndex函数可以复用到此函数中去
 * @param  {Array} arr
 * @param  {String=} key
 * @param  {*} value
 * @param  {*} newData
 * @return {Boolean}
 */
function replaceElem(arr, key, value, newData) {
    var i;
    if (arguments.length === 3) {
        newData = value
        value = key
        for (i = 0; i < arr.length; i++) {
            if (arr[i] === value) {
                arr[i] = newData
                return true
            }
        }
    } else {
        for (i = 0; i < arr.length; i++) {
            if (arr[i][key] === value) {
                arr[i] = newData
                return true
            }
        }
    }
    return false
}
/**
 *
 * 数据和dom之间的相互操作
 * @namespace dataMod
 * @mixin
 * @type {Object}
 */
var dataMod = {
    /**
     * 根据传递进来的elem获取或插入数据
     *
     * @param  {Element} elem
     * @param  {String} key
     * @param  {*=} value
     */
    data: function(elem, key, value) {
        var isObj = getType(key) === 'object'

        if (arguments.length === 2 && !isObj)
            return (elem[$jcData] || UniObj)[key];

        if (!elem[$jcData])
            elem[$jcData] = {};
        if (isObj) {
            var k;
            for (k in key) {
                if (key.hasOwnProperty(k)) {
                    elem[$jcData][k] = key[k]
                }
            }
        } else {
            elem[$jcData][key] = value;
        }
    },
    /**
     * 根据key判定该elem下是否存在数据
     *
     * @param  {Element}  elem
     * @param  {string}  key
     * @return {Boolean}
     */
    hasData: function(elem, key) {
        return key in (elem[$jcData] || UniObj);
    },
    /**
     * 删除数据
     * @param  {Element} elem
     * @param  {String} key
     */
    removeData: function(elem, key) {
        if (this.hasData(elem, key))
            delete elem[$jcData][key];
    }
}
/**@module documentParse*/

var rmsAttr = /jc-(\w+)-?(.*)/;
var stopScan = oneObject("area,base,basefont,br,col,command,embed,hr,img,input,link,meta,param,source,track,wbr,noscript,script,style,textarea".toUpperCase())

//var ons = oneObject("animationend,blur,change,click,dblclick,focus,keydown,keypress,keyup,mousedown,mouseenter,mouseleave,mousemove,mouseout,mouseover,mouseup,scroll");
var openTag, closeTag, rexpr, rexprg, rbind, needWatchTag = '#';


var rfilters = /\|\s*(\w+)\s*(\([^)]*\))?/g,
    r11a = /\|\|/g,
    r11b = /U2hvcnRDaXJjdWl0/g;

/**
 * attribute扫描先后顺序
 *
 * @readOnly
 * @type {Object}
 */
var attrPriority = {
    "reuse": 120,
    "if": 100,
    "repeat": 80,
    "dupfilter": 60,
    "data": 90
};


var dParseErr = minErr("document parse");

/**
 * 更换表达式包裹符,注意，数组两个值不能相等
 *
 * @alias jc.config
 * @name  interpolate
 * @example
 * interpolate(['_#','#_'])
 *
 * @param  {array} array  notice:array[0]!==array[1]
 */
config.interpolate = function(array) {
    if (Array.isArray(array) && array[0] && array[1] && array[0] !== array[1]) {

        openTag = array[0]
        closeTag = array[1]
        var o = openTag,
            c = closeTag;

        rexpr = new RegExp(o + "(.*?)" + c)
        rexprg = new RegExp(o + "(.*?)" + c, "g")
        rbind = new RegExp(o + ".*?" + c + "|\\sjc-")
    }
}
config.interpolate(['{{', "}}"])


/**
 * 扫描dom元素的attributes
 * @namespace documentParse
 * @param  {Element} elem
 * @param  {object} data
 * @param  {tempHelper.tempHelper} th
 */
function scanAttr(elem, data, th) {
    var match, attributes = elem.attributes,
        bindings = [];

    for (var i = 0, attr; attr = attributes[i++];) {
        if (match = attr.name.match(rmsAttr)) {
            var type = match[1]
            var param = match[2] || ""
            var binding = {
                type: type,
                param: param,
                element: elem,
                name: match[0],
                value: attr.value,
                priority: attrPriority[type] || 0
            }
            if (param === 'loop' && type === 'if') {
                binding.priority -= 30
            }
            bindings.push(binding)
            //}
        }
    }

    if (bindings[0]) {

        bindings = bindings.sort(function(v1, v2) {
            return v2.priority - v1.priority
        })
        var firstBind = bindings[0]
        scanDispatch(bindings, data, th) //bindings会被修改

        if (firstBind.type === 'reuse' && firstBind.param === 'hide') {
            return;
        }
    }
    if (elem.parentNode && !stopScan[elem.tagName] && rbind.test(elem.innerHTML /*.replace(rlt, "<").replace(rgt, ">")*/ )) {
        scanNodes(elem, data, th) //扫描子孙元素
    }
}

/**
 * 扫描element子结点
 * @namespace documentParse
 * @param  {Element} parent
 * @param  {object} data
 * @param  {tempHelper.tempHelper} th
 */
function scanNodes(parent, data, th) {
    var node = parent.firstChild
    while (node) {
        var nextNode = node.nextSibling
        if (node.nodeType === 1) {
            // scanTag(node, data)
            scanAttr(node, data, th)
        } else if (node.nodeType === 3) {
            if (rexpr.test(node.data)) {
                scanText(node, node.data, data, th)
            }
        } else if (node.nodeType === 8) {
            // if (rexpr.test(node.innerHTML)) { 
            //     scanText(node, node.innerHTML, data, th)
            // }
        }
        node = nextNode
    }
}


/**
 * 扫描文本节点
 * @namespace documentParse
 * @param  {textNode} textNode
 * @param  {String} text
 * @param  {object} data
 * @param  {tempHelper.tempHelper} th
 */
function scanText(textNode, text, data, th) {
    var bindings = [],
        tokens = scanExpr(text)
    if (tokens.length) {
        for (var i = 0, token; token = tokens[i++];) {
            var node = document.createTextNode(token.value) //将文本转换为文本节点，并替换原来的文本节点
            if (token.expr) {
                var binding = {
                    type: "text",
                    node: node,
                    nodeType: 3,
                    value: token.value,
                    needWatch: token.needWatch,
                    filters: token.filters
                }

                bindings.push(binding) //收集带有插值表达式的文本
            }
            documentFragment.appendChild(node)
        }
        textNode.parentNode.replaceChild(documentFragment, textNode)
        scanDispatch(bindings, data, th)
    }
}

/**
 * @typedef {Object} Filter
 * @property {String} method  jc.filters的方法名称
 * @property {Array.<String>} args 运行jc.filters[method]的参数
 *
 */

/**
 * @typedef {Array.<Object>} textExpr
 * @property {boolean} expr 是否是表达式
 * @property {String} value 表达式的值
 * @property {boolean=} needWatch 需不需要做监控处理
 * @property {Array.<Filter>=} filters 过滤器集合
 *
 */

/**
 * 处理表达式值，过滤出filters和需要绑定的属性
 * @namespace documentParse
 * @param  {String} str
 * @return {Object}
 */
function scanExpr(str) {
    var tokens = [],
        value, start = 0,
        stop
    do {
        stop = str.indexOf(openTag, start)
        if (stop === -1) {
            break;
        }
        value = str.slice(start, stop)
        if (value) { // {{ 左边的文本
            tokens.push({
                value: value,
                expr: false
            })
        }
        start = stop + openTag.length
        stop = str.indexOf(closeTag, start)
        if (stop === -1) {
            break;
        }
        value = str.slice(start, stop)
        if (value) { //处理{{ }}插值表达式
            var needWatch = false
            if (!value.indexOf(needWatchTag)) { //过滤需要监控的标签
                value = value.replace(needWatchTag, "")
                needWatch = true
            }
            var leach = []
            if (~value.indexOf("|")) { // 抽取过滤器 先替换掉所有短路与
                value = value.replace(r11a, "U2hvcnRDaXJjdWl0") //btoa("ShortCircuit")
                value = value.replace(rfilters, function(c, d, e) {
                    var args = [];
                    if (e) {
                        e.slice(1, -1).replace(rword, function(v) {
                            args.push(v.trim());
                        })
                    }
                    leach.push({
                        method: d,
                        args: args
                    })
                    return ""
                })
                value = value.replace(r11b, "||") //还原短路与
            }
            tokens.push({
                value: value,
                expr: true,
                needWatch: needWatch,
                filters: leach.length ? leach : void 0
            })
        }
        start = stop + closeTag.length
    } while (1)
    value = str.slice(start)
    if (value) { //}} 右边的文本
        tokens.push({
            value: value,
            expr: false
        })
    }

    return tokens
}


// function getCurrentTmp() {
//     return getCurrentTmp.tmp
// }

/**
 * 渲染页面的核心方法
 * 根据url去抓模板，然后将数据渲染进模板，并初始化模板运行状态，
 * 注意: $div都是独一无二的，如果同一个$div进行二次渲染的话，
 * 模板运行状态会被重置，其他模板 关于此模板的数据订阅也会被重置
 * 需要重新订阅
 *
 * @memberOf documentParse
 * @param  {object}   data
 * @param  {string=}   url     url
 * @param  {Node}     $div     the jquery node to render in
 * @param  {funciton=} callback
 * @param {object=} options  配置，暂时没有用处
 */
function render(data, url, $div, callback, options) {
    if (typeof arguments[1] !== 'string') {
        callback = $div
        $div = url
    }
    var ct = getType(callback)

    if (ct === 'object')
        options = ct
    if (ct !== 'function')
        callback = noop
    var th = tempHelper($div);
    getBufferedView(url, function(content) {
        scan(content)
        callback(th)
    });

    function scan(text) {
        var elem = $div[0];
        if (text) {
            th.empty();
            th.html(text);
            th._data("data", data)
            th._data("id", $nextUid())
            th._data("url", url)
            scanNodes($div[0], data, th);
        }
        //elem.style.visibility = ""
    }
}

// function renderWithoutUrl
var ajaxConfig = config.ajax = {
    defaultSuccessCheck: function(data, status) {
        if (data && !data.Succeed && data.SessionTimeout == 1) {
            alert("会话已经失效，请您重新登录");
            window.location.href = "./";
            return false
        }
        return true
    },
    defaultErrorCallback: function(xhr, status, error) {
        if (xhr.statusText != 'success') {
            alert('showErrorToast' + '请求超时或网络问题,' + status || error);
        }
    },
    getView: getView
};
var templates = jc.templates = {};

function getView(view, callback, errorCallback) {
    $.ajax({
        type: "GET",
        url: config.tplRootUrl + view,
        timeout: 10000,
        cache: false,
        success: function(data, status) {
            if (ajaxConfig.defaultSuccessCheck(data, status)) {
                if (typeof callback == "function") {
                    callback(data);
                }
            }

            // if (data && !data.Succeed && data.SessionTimeout == 1) {
            //     alert("会话已经失效，请您重新登录");
            //     window.location.href = "./";
            //     return;
            // }
        },
        error: function(xhr, status, error) {
            if (errorCallback && (typeof errorCallback == "function")) {
                errorCallback(xhr, status, error);
            } else {
                ajaxConfig.defaultErrorCallback(xhr, status, error)
            }
        }
    });
}

function getBufferedView(view, callback) {
    if (!templates[view]) {
        ajaxConfig.getView(view + ".html", function(data) {
            templates[view] = data;
            callback(data);
        }, function() {
            alert('get view failed');
        });
    } else {
        callback(templates[view]);
    }
}
var rKey = /^[$_a-zA-A][$._a-zA-Z\d]*$|^[ ]{0,1}$/,
    // isTextBind = new RegExp("jc-bind[ ]+"),
    filterErr = minErr("filter"),
    scanErr = minErr('scan');

var removableAttr = oneObject("checked,selected");
var jcInnerEvent = 'jcInnerEvent';

/**
 * 根据传递进来的数据,判定当前路径是否是在`jc-repeat`标签下的，
 * 然后做相应的处理，并返回真正的索引路径
 *
 * @namespace parseAnalyze
 * @param  {Object} data
 * @param  {string} path
 * @return {string}
 */
function repeatPathCheck(data, path) {
    if (data.$isForEach) {
        var p = path.split(".")
        if (p.length > 1) {
            p = p[0]
            path = data[p].$path + "." + data[p].$index + path.substring(path.indexOf("."), path.length)
        } else {
            path = data.$path + "." + data.$index
        }

    }
    return path
}
/**
 * 根据 `bind.type`划分到不同的模块中，
 * 进行相应的处理
 *
 * TODO:更改为管道过滤
 * @namespace parseAnalyze
 * @param  {array} binds
 * @param  {object} data
 */
function scanDispatch(binds, data, th) {
    var type, bind

    while (bind = binds.shift()) {

        type = bind.type
        if (bindMod[type]) {
            bindMod[type](th, bind, data, binds)
        } else {
            if (!scanMod[type]) {
                if (type === 'id')
                    filterErr('Do not support jc-id! because this emits great bad smell of coding.')
                bind.param = bind.type
                bind.type = type = 'cal'
            }
            scanMod[type](th, bind, data, binds)
        }
    }
}

function exprRunner(data, value, filters) {
    if (arguments.length === 2) {
        var valExpr = scanExpr(openTag + value + closeTag, data)[0];
        value = valExpr.value
        filters = valExpr.filters
    }
    if (rKey.test(value)) {
        value = dotPath(data, value)
    } else {
        value = Function.apply(noop, ["return " + value]).call(data)
    }
    return filterRunner(value, filters)
}

function filterRunner(value, filters) {
    if (filters) {
        for (var i = 0, f; f = filters[i++];) {
            if (typeof filtersMod[f.method] === "function") {
                value = filtersMod[f.method].apply(null, [value].concat(f.args))
            } else {
                filterErr("jc.filters does not hava {1} method or jc.filters.{1} is not a function", f.method)
            }
        }
    }
    return value
}

var bindMod = jc.bindMod = {
    reuse: function(th, bind, data, binds) {
        var elem = bind.element;

        var fragInfo = {
                tpl: bind.element.innerHTML,
                elem: elem
            },
            id = bind.value;

        var frags = th._data("frags")
        if (frags) {
            frags[id] = fragInfo
        } else {
            var reuseFrag = {}
            reuseFrag[id] = fragInfo
            th._data("frags", reuseFrag)
        }
        if (bind.param === 'hide') {
            elem.style.display = 'none'
            binds.length = 0
        }
        bind.element.removeAttribute(bind.name)
    },
    bind: function(th, bind, data) {
        var elem = bind.element,
            bindType = bind.param,
            valExpr = scanExpr(openTag + bind.value + closeTag, data)[0],
            path = repeatPathCheck(data, valExpr.value).split("."),
            filters = valExpr.filters,
            defaultTriggerEvent = 'input';

        if (elem.nodeName === 'SELECT') {
            defaultTriggerEvent = 'change'
        } else if (elem.type === 'radio') {
            defaultTriggerEvent = 'click'
        }
        //看看能不能重构简化
        if (elem.type === 'checkbox') { //checkbox 特殊处理
            elem.addEventListener(bindType || 'click', function(event) {
                arrPathVal(th._data("data"), path, filterRunner(Boolean(event.target.checked), filters))
            })
            elem.addEventListener(jcInnerEvent, function() {
                arrPathVal(th._data("data"), path, filterRunner(Boolean(event.target.checked), filters))
            })
        } else if (elem.type === 'number') {
            elem.addEventListener(bindType || defaultTriggerEvent, function(event) {
                arrPathVal(th._data("data"), path, filterRunner(Number(event.target.value), filters))
            })
            elem.addEventListener(jcInnerEvent, function() {
                arrPathVal(th._data("data"), path, filterRunner(Number(event.target.value), filters))
            })
        } else {
            elem.addEventListener(bindType || defaultTriggerEvent, function(event) {
                arrPathVal(th._data("data"), path, filterRunner(event.target.value, filters))
            })
            elem.addEventListener(jcInnerEvent, function() {
                arrPathVal(th._data("data"), path, filterRunner(event.target.value, filters))
            })
        }
        elem.removeAttribute(bind.name)
    },
    duplex: function(th, bind, data) {
        this.bind(th, bind, data)
        watchDispatcher(th, bind, data, exprRunner(data, bind.value))
    },
    dupfilter: function(th, bind, data) {
        var elem = bind.element
        elem.removeAttribute(bind.name)
        dataMod.data(elem, "dupfilter", bind.value)
    },
    reset: function(th, bind, data) {
        var elem = bind.element,
            bindType = bind.param;
        elem.addEventListener(bindType || 'click', function() {
            var form = elem.form,
                e = document.createEvent('HTMLEvents');
            e.initEvent(jcInnerEvent, false, true)
            form.reset()
            for (var i = 0, f; f = form[i++];) {
                f.dispatchEvent(e)
            }
        })
        //预防用错
        // if (elem.nodeName === 'INPUT') {
        //     elem.type = 'button'
        // }
    },
    attr: function(th, bind, data) {
        if (bind.param === 'class')
            scanErr(" oops! now we not support the class tag bind")
        var elem = bind.element

        elem.removeAttribute(bind.name)
        elem.setAttribute(bind.param, exprRunner(data, bind.value))
        watchDispatcher(th, bind, data)
    },
    repeat: function(th, bind, data, binds) {
        // type: type,
        // param: param,
        // element: elem,
        // name: match[0],
        // value: attr.value,

        var eleData, value,
            index = bind.param,
            tpl = bind.element,
            parentNode = tpl.parentNode;

        binds.length = 0 //不再执行剩下 绑定,留给元素自己渲染执行

        if (data.$isForEach) {
            eleData = data
        } else {
            eleData = {
                $isForEach: true,
                $data: data
            }
        }
        value = dotPath(data, bind.value)
        //use regex to improve performance
        var $path = bind.value.split(".")
        var pathHead = $path.shift()
        if (pathHead === '$data') {
            $path = $path.join('.')
        } else {
            var parentPath = (eleData[pathHead] || UniObj).$path
            if (parentPath !== undefined) {
                parentPath = parentPath + "." + eleData[pathHead].$index
                $path.unshift(parentPath)
                $path = $path.join(".")
            } else {
                $path = bind.value
            }
        }

        if (value === undefined) {
            scanErr("the `jc-repeat` tag value in tempate {1} should not undefined! the path is {2}", th._data("url"), $path)
        }

        var frag = document.createDocumentFragment();
        tpl.removeAttribute(bind.name);
        var startComment = document.createComment("jc-repeat start")
        frag.appendChild(startComment)
        if (value.length === 0) {
            eleData.$path = $path
        } else {
            for (var i = 0; i < value.length; i++) {
                eleData[index] = value[i]
                if (typeof value[0] === 'object') {
                    eleData[index].$index = i
                    eleData[index].$path = $path
                } else {
                    eleData.$index = i
                    eleData.$path = $path
                }

                var newElem = tpl.cloneNode(true)
                frag.appendChild(newElem)
                scanAttr(frag.lastChild, eleData, th)
            }
        }
        var endComment = document.createComment("jc-repeat end") //用来做length＝0时的批量优化
        frag.appendChild(endComment)
        parentNode.replaceChild(frag, tpl)

        bind.startComment = startComment
        bind.endComment = endComment

        watchDispatcher(th, bind, eleData)
    },
    foreach: function(th, bind, data) {
        var parentNode = bind.element,
            tpl = parentNode.children,
            index = bind.param,
            tplArr = [],
            eleData,
            value, i;

        for (i = 0; i < tpl.length; i++) {
            tplArr.push(tpl[i])
        }
        parentNode.innerHTML = ""
        if (data.$isForEach) {
            eleData = data
        } else {
            eleData = {
                $isForEach: true,
                $data: data
            }
        }
        value = dotPath(data, bind.value)
        if (value === undefined) {
            scanErr("the `jc-foreach` tag value in tempate {1} should not be undefined! the path is {2}", th._data("url"), $path)
        }

        var $path = bind.value.split(".")
        var pathHead = $path.shift()
        if (pathHead === '$data') {
            $path = $path.join('.')
        } else {
            var parentPath = (eleData[pathHead] || UniObj).$path
            if (parentPath !== undefined) {
                parentPath = parentPath + "." + eleData[pathHead].$index
                $path.unshift(parentPath)
                $path = $path.join(".")
            } else {
                $path = bind.value
            }
        }
        var frag = document.createDocumentFragment()
        for (i = 0; i < value.length; i++) {
            eleData[index] = value[i]
            if (typeof value[0] === 'object') {
                eleData[index].$index = i
                eleData[index].$path = $path
            } else {
                eleData.$index = i
                eleData.$path = $path
            }
            for (var j = 0; j < tplArr.length; j++) {
                frag.appendChild(tplArr[j].cloneNode(true))
                scanAttr(frag.lastChild, eleData, th)
            }
        }
        parentNode.appendChild(frag)
        bind.tpl = tplArr
        watchDispatcher(th, bind, eleData)

    },
    if : function(th, bind, data, binds) {
        //  type: type,
        // param: param,
        // element: elem,
        // name: attr.name,
        // value: attr.value
        var value = !!exprRunner(data, bind.value),
            originValue = value,
            elem = bind.element;

        /*if (getType(value) !== 'boolean') {
            filterErr("the value {1} must be boolean type", value)
        }*/
        binds.length = 0
        if (bind.param === 'not')
            value = !value
        elem.removeAttribute(bind.name)
        var replaceChild;
        if (!value) {
            replaceChild = document.createComment("if-tag")
            elem.parentNode.replaceChild(replaceChild, elem)
        } else {
            replaceChild = elem.cloneNode(true)
            elem.parentNode.replaceChild(replaceChild, elem)
            scanAttr(replaceChild, data, th)
        }
        bind.realElem = replaceChild
        if (rKey.test(bind.value))
            watchDispatcher(th, bind, data, originValue)
    }
}

var scanMod = jc.scanMod = {
    text: function(th, bind, data) {
        var path = bind.value
        var v = exprRunner(data, path, bind.filters);
        bind.node.nodeValue = v
        if (bind.needWatch)
            watchDispatcher(th, bind, data, v)
    },
    cal: function(th, bind, data) {
        var elem = bind.element,
            attrKey = bind.name,
            rendAttrKey = bind.param;
        elem.removeAttribute(attrKey)
        var value = exprRunner(data, bind.value);


        if (rendAttrKey in removableAttr) {
            if (value)
                elem.setAttribute(rendAttrKey, '')
            else
                elem.removeAttribute(rendAttrKey)
        } else {
            elem.setAttribute(rendAttrKey, value)
        }
    },
    /**
     * class比较灵活
     * jc-class=表达式 会将表达式的结果 塞入本元素的className中
     * jc-class-name=表达式,如果表达式的结果在if中判定为true,
     * 则会将name塞入本元素的className中，否则，就将该name从
     * 本元素剔除出去
     *
     */
    "class": function(th, bind, data) {
        var elem = bind.element,
            addClass = bind.param,
            value = exprRunner(data, bind.value),
            originClass = elem.className;
        elem.removeAttribute(bind.name)
        if (addClass === '') {
            if (value !== '' && !originClass.match(new RegExp("(^|\\s)" + value + "($|\\s)"))) {
                elem.className = originClass + " " + value
            }
        } else {
            var classMatchReg = new RegExp("(^|\\s)" + addClass + "($|\\s)");
            if (value) {
                if (!originClass.match(classMatchReg)) {
                    elem.className = originClass + " " + addClass
                }
            } else {
                elem.className = originClass.replace(classMatchReg, "")
            }
        }


    },
    value: function(th, bind, data) {
        var elem = bind.element,
            attrKey = bind.name;
        var value = exprRunner(data, bind.value);
        elem.removeAttribute(attrKey)

        if (elem.nodeName === 'TEXTAREA') {
            elem.value = value
        } else
            elem.setAttribute('value', value)
    }

}

var filtersMod = jc.filters = {

    /**
     * @example
     * {{value | defaultVal dv}}
     * 默认值配置 如果value 为 null , undefined 则会替换成默认值
     *
     * @param  {string} value         内部系统参数
     * @param  {string=} dv           默认值为""
     * @return {string}
     */
    defaultVal: function(value, dv) {
        if (value === undefined || value === null)
            return dv || "";
        return value
    },

    toBoolean: function(value, equalVal) {
        if (equalVal) {
            if (value === equalVal)
                return true
            return false
        }
        if (value)
            return true
        return false
    },
    sampleMap: function(value) {
        var length = arguments.length
        if (length % 2 === 0) {
            filterErr("the number of arguments({1})is wrong, please check it", length - 1)
        }
        var o = {};
        for (var i = 1; i < length; i = i + 2) {
            o[arguments[i]] = arguments[i + 1]
        }
        return o[value + ""]
    }
}
var $promiseErr = minErr('promise');

/* complete can be used for only one time;
 * whatever,wait can use in any place;
 * then is what waiting complete or wait function finish and then
 * exec;
 * err handle the possible exception which may be casted by complete or wait;
 */
function promise() {
    var that = this;
    p = function() {

    };
    that._callback = [];
    that.isComplete = false;
    that.err = undefined;
    that.errfunc = undefined;
    p.prototype = {
        then: function(func) {
            if (that.err === undefined) {
                if (that.isComplete && that._callback.length === 0) {
                    try {
                        that.result = func(that.result);
                    } catch (e) {
                        that._callback = [];
                        if (that.errfunc !== undefined) {
                            that.errfunc(e);
                        } else {
                            that.err = e;
                        }
                    }
                } else {
                    that._callback.push(func);
                }
            }
            return this;
        },
        wait: function(fv, time) {
            var p = promise();
            setTimeout(function() {
                p.complete(fv);
            }, time);
            while (that._callback.length !== 0) {
                p.then(that._callback.shift());
            }
            p.error(that.errfunc);
            return p;
        },
        complete: function(fv) {
            try {
                if (typeof fv === 'function') {
                    that.result = fv();
                } else {
                    that.result = fv;
                }
            } catch (e) {
                that.result = e;
                that.err = true;
            }

            that.isComplete = true;
            while (that._callback.length > 0) {
                that.result = that._callback.shift()(that.result);
            }
            return this;
        },
        error: function(func) {
            if (that.err) {
                try {
                    func(that.err);
                } catch (e) {
                    $promiseErr('the error method should not cast exception {1}', e);
                }
            } else {
                that.errfunc = func;
            }

        }
    };
    return new p();
}
/**
 * 发布订阅系统
 * @namespace pubsub
 */
/**
 * 创建一个发布订阅系统
 * @memberOf pubsub
 * @return {object} 独立的发布订阅系统
 */
function initPB() {

    var Observable = {
        /**
         * 发布消息
         *
         * @param  {string} eventName
         * @param  {object|string} data
         * @return {this}
         */
        publish: function(eventName, data) {
            this.$que.forEach(function(q) {
                if (q[eventName]) {
                    //队列在执行的时候，可能会遭遇到解绑自身绑定函数，导致后面相连的没有被执行
                    q[eventName].concat().forEach(function(fn) {
                        fn(data, eventName);
                    })
                }
            })
            return this;
        },
        /**
         * 解除订阅，可解除整个管道，或具体到一个具体函数
         * @param  {string} eventName
         * @param  {string=} fnName    可选的 function name
         * @return {this}
         */
        unbind: function(eventName, fnName) {
            var re = [];
            if (eventName === '$all') {

                for (var i = 0, channelName; channelName = this.$channel[i++];)
                    if (channelName in this.$jcQueue) {
                        re.push(this.$jcQueue[channelName])
                        delete this.$jcQueue[channelName]
                    }
                    //TODO: return what
                return re;
            }
            this.$que.forEach(function(q) {
                if (fnName) {
                    q[eventName].forEach(function(fn, index, arr) {
                        if (fn.fnName === fnName)
                            re.push(arr.splice(index, 1))
                    })
                } else {
                    re = q[eventName]
                    delete q[eventName]
                }
            })
            return re;
        },
        /**
         * 通过正则，获取匹配的eventName
         *
         * @param  {Object} reg
         * @return {Array}
         */
        eventNamesWithReg: function(reg) {
            var re = [];
            if (typeof reg === 'string')
                reg = new RegExp(reg)
            this.$que.forEach(function(queue) {
                for (var eventName in queue) {
                    if (reg.test(eventName))
                        re.push(eventName)
                }
            })
            return re
        },
        /**
         * 订阅事件，函数名称可不填,函数名称也可以用来归类
         * @param  {string }   eventName
         * @param  {string=}   fnName  option
         * @param  {function} fn
         * @return {this}
         */
        bind: function(eventName, fnName, fn) {
            // TODO 重复的剔除

            if (fn === undefined) {
                fn = fnName
            } else {
                fn.fnName = fnName
            }

            this.$que.forEach(function(que) {
                var eventArr = que[eventName];
                if (!eventArr)
                    eventArr = que[eventName] = [];
                eventArr.push(fn)
            })
            return this;
        }
    };

    /**
     * 进入channel，channel如果不存在，则创建
     * @param  {array|string} queues 传入数组或者"ab,v,d"字符串，当传参为 "$all"/["$all"]时，表示选择所有channel
     * @return {object}        返回Observable关于订阅的操作方法
     */
    function pb(queues) {
        var $que = [],
            jcQueue = pb.fn.$jcQueue;
        //var re = {};

        if (getType(queues) === 'string') {
            queues = queues.match(rword)
        }
        if (queues[0] === '$all') {
            if (Object.keys) {
                $que = Object.keys(jcQueue);
            } else {
                var key
                for (key in jcQueue) {
                    $que.push(key);
                }
            }
        } else {
            // TODO:重复值
            queues.forEach(function(q) {
                $que.push(jcQueue.hasOwnProperty(q) ? jcQueue[q] : jcQueue[q] = {})
            })

        }
        pb.fn.$que = $que;
        pb.fn.$channel = queues;

        return pb.fn
    }

    jc.mix(pb.fn = {}, Observable)
    pb.fn.$jcQueue = {}
    return pb
}
var watchErr = minErr("watch Data");

function checkIsInDom(elem, id, path, cancelFunc, callback) {
    if (!isInDocument(elem)) {
        var nextId = $nextUid()
        cancelFunc.fnName = nextId
        $watchPB(id).unbind(path, nextId)
    } else {
        callback()
    }
}

//如何使用异步提高效率
function watchDispatcher(th, bind, data, value) {
    // var currentTmp = getCurrentTmp()
    var type = bind.type;
    if (type === "if") {
        watchIfChange(bind, data, value, th)
    } else if (type === "repeat" || type === 'foreach') {
        watchCollectionChange(bind, data, th)
    } else if (bind.nodeType === 3) {
        watchByText.call(1, bind, data, value, th)
    } else if (type === 'duplex') {
        watchByDuplex.call(1, bind, data, value, th)
    } else if (type === 'attr') {
        watchByAttr(bind, data, th)
    }
}

// function _async(fn) {
//     setTimeout(fn, 0)
// }

var $watchPB = jc.$watchPB = initPB();

function textProxy(elem, id, path, filters) {
    var response = (function(val) {
        checkIsInDom(elem, id, this.path, response, function() {
            elem.nodeValue = filterRunner(val, filters)
        })
    }).bind({
        path: path
    })
    return response
}

function watchByText(bind, data, value, th) {
    // type: "text",
    // node: node,
    // nodeType: 3,
    // value: token.value,
    // filters: filters
    var elem = bind.node,
        path = repeatPathCheck(data, bind.value),
        filters = bind.filters,
        id = th._data("id");
    $watchPB(id).bind(path, textProxy(elem, id, path, filters))
    th.listen(path, th.$div)
}

function duplexProxy(elem, id, path, filters) {
    var response = (function(val) {
        checkIsInDom(elem, id, this.path, response, function() {
            switch (elem.nodeName) {
                case "INPUT":
                    switch (elem.type) {
                        case "checkbox":
                        case "radio":
                            elem.checked = filterRunner(val, filters)
                            break;
                        default:
                            elem.value = filterRunner(val, filters)
                    }
                    break;
                case "TEXTAREA":
                case "SELECT":
                    elem.value = filterRunner(val, filters)
            }
        })
    }).bind({
        path: path
    })
    return response
}

function watchByDuplex(bind, data, value, th) {
    var elem = bind.element,
        valExpr = scanExpr(openTag + bind.value + closeTag, data)[0],
        path = repeatPathCheck(data, valExpr.value),
        filters = valExpr.filters,
        id = th._data("id")
        //dataKey = "oldValue"
    if (filters) {
        var filterExpr = dataMod.data(elem, "dupfilter")
        if (!filterExpr) {
            watchErr("if jc-duplex has filters, the node must have jc-dupfilter to declare the reverse filters of jc-duplex filters ")
        }
        filters = scanExpr(openTag + filterExpr + closeTag, data)[0].filters
    }

    //dataMod.data(elem, dataKey, value)
    $watchPB(id).bind(path, duplexProxy(elem, id, path, filters))
    th.listen(path, th.$div)
}

function attrProxy(elem, id, path, param, filters) {
    var response = (function(val) {
        checkIsInDom(elem, id, this.path, response, function() {
            if (param === 'class') {
                //TODO: 关于 class处理
            } else {
                elem.setAttribute(param, filterRunner(val, filters))
            }
        })
    }).bind({
        path: path
    })
    return response
}

function watchByAttr(bind, data, th) {
    var elem = bind.element,
        id = th._data("id"),
        valExpr = scanExpr(openTag + bind.value + closeTag, data)[0],
        path = repeatPathCheck(data, valExpr.value),
        filters = valExpr.filters,
        response = attrProxy(elem, id, path, bind.param, filters);
    $watchPB(id).bind(path, response)
    th.listen(path, th.$div)
}

function ifProxy(th, data, bind, path, filters, id) {
    var elem = bind.element,
        realElem = bind.realElem,
        param = bind.param;
    var response = (function(val) {
        val = !!filterRunner(val, filters)
        var path = this.path
        checkIsInDom(realElem, id, path, response, function() {
            var originVal = val
            if (param === 'not')
                val = !val
            var replaceElem;
            if (val) {
                if (realElem.nodeType === 8) {
                    replaceElem = elem.cloneNode(true)
                    realElem.parentNode.replaceChild(replaceElem, realElem)
                    realElem = replaceElem
                    var parentPath = getParentPath(path)
                    var parentData = dotPath(th.data(), parentPath)
                    var index = bind.value.split(".")[0]
                    data[index] = dotPath(th.data(), parentPath)

                    scanAttr(replaceElem, data, th)
                }
            } else {
                if (realElem.nodeType !== 8) {
                    replaceElem = document.createComment("if-tag")
                    realElem.parentNode.replaceChild(replaceElem, realElem)
                    realElem = replaceElem
                }
            }
        })
    }).bind({
        path: path
    })
    return response
}

function watchIfChange(bind, data, value, th) {
    var elem = bind.element,
        realElem = bind.realElem,
        valExpr = scanExpr(openTag + bind.value + closeTag, data)[0],
        filters = valExpr.filters,
        path = repeatPathCheck(data, valExpr.value),
        // dataKey = "isShow",
        param = bind.param,
        id = th._data("id");
    //dataMod.data(elem, dataKey, value)
    $watchPB(id).bind(path, ifProxy(th, data, bind, path, filters, id))
    th.listen(path, th.$div)
}


/**
 * 以element为开始，向下遍历到index同辈元素，
 * 当number被传入时，返回向下number数量的同辈元素，
 * 不number被传入时，返回该遍历到的同辈元素
 * @param  {Element|Node} element
 * @param  {Number} index
 * @param  {Number=} number
 * @return {array}
 */
function getNextSibilings(element, index, number) {

    var re = element
    while (index--) {
        re = re.nextSibling
    }
    re = [re]
    if (arguments.length === 3) {
        var el = re[0]
        while (--number) {
            el = el.nextSibling
            re.push(el)
        }
    }
    return re
}


//TODO: string 转化成正则时的处理
function collectionProxy(th, data, bind, path, id) {
    //realElem ==nodeType 8
    // var realElem = bind.realElem
    var startComment = bind.startComment,
        endComment = bind.endComment,
        response;

    if (startComment) {
        response = (function(collectionData) {
            var path = this.path,
                tpl = bind.element;
            checkIsInDom(startComment, id, path + '.length', response, function() {

                var frag = document.createDocumentFragment(),
                    parentElem = startComment.parentNode,
                    dataLength = collectionData.length,
                    param = bind.param;

                //渲染模版
                for (var i = 0; i < collectionData.length; i++) {
                    data[param] = collectionData[i]
                    if (typeof data[param] !== 'object') {
                        data.$index = i
                        data.$path = path
                    } else {
                        data[param].$index = i
                        data[param].$path = path
                    }
                    frag.appendChild(tpl.cloneNode(true))
                    scanAttr(frag.lastChild, data, th)
                }
                while (startComment.nextSibling !== endComment) {
                    parentElem.removeChild(startComment.nextSibling)
                }
                parentElem.insertBefore(frag, endComment)

            })
        }).bind({
            path: path
        })
    } else {
        //foreach

        response = (function(collectionData) {

            var frag = document.createDocumentFragment(),
                parentElem = bind.element,
                path = this.path,
                param = bind.param,
                tpl = bind.tpl;
            for (var i = 0; i < collectionData.length; i++) {
                data[param] = collectionData[i]
                if (typeof data[param] !== 'object') {
                    data.$index = i
                    data.$path = path
                } else {
                    data[param].$index = i
                    data[param].$path = path
                }
                for (var j = 0; j < tpl.length; j++) {
                    frag.appendChild(tpl[j].cloneNode(true))
                    scanAttr(frag.lastChild, data, th)
                }
            }
            parentElem.innerHTML = ""
            parentElem.appendChild(frag)
        }).bind({
            path: path
        })
    }
    return response
}
/**
 * 观察collection length 变化，触发页面绑定变话
 * @param  {object} bind
 * @param  {object} data
 * @param  {object} th
 */
function watchCollectionChange(bind, data, th) {
    // type: type,
    // param: param,
    // element: elem,
    // name: match[0],
    // value: attr.value,
    // realElem..
    var param = bind.param,
        tpl = bind.element,
        id = th._data("id");
    var path = data.$path || data[param].$path;

    $watchPB(id).bind(path + '.length', collectionProxy(th, data, bind, path, id))
    th.listen(path, th.$div)
    // $watchPB(currentTmp._data("id"))    
}
})(window,document);